// $Id$
// Implementation of the extended camera to allow for easier camera control
//
// ml278
// Copyright (C) 2008 Bjørn Øivind Bjørnsen, Aleksander Gjermundsen, Knut Andre Tidemann, Thomas Hansen
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

#include "extendedcamera.h"

#include "Ogre.h"

using namespace Ogre;

namespace ml278 {
	/**
	 * Constructor
	 * @param name name of the camera
	 * @param mgr ogre scenemanager
	 * @post manual control has been disabled
 	*/
	ExtendedCamera::ExtendedCamera(String name, SceneManager *mgr) :
			OctreeCamera(name + "_Camera", mgr)
	{
		this->setNearClipDistance(1);
		mCameraNode = mgr->getRootSceneNode()->createChildSceneNode(name + "_Node");
		mCameraNode->attachObject(this);
		mTargetNode = NULL;
		mFollowNode = NULL;
		mSceneMgr = mgr;
		
		mMovementStep = 100.0;
		
		for(int i=0;i<4;i++)
		{
			mMovementDir[i] = false;
		}
	}
	
	/**
	 * Destructor
 	*/
	ExtendedCamera::~ ExtendedCamera()
	{
		mCameraNode->detachAllObjects();
		mSceneMgr->destroySceneNode(mCameraNode->getName());
		if(mTargetNode)
			mTargetNode->removeAndDestroyChild(mFollowNode->getName());
		// Do not destroy mTargetNode on account of it being owned by someone else!
	}
	
	/**
	 * Sets the position of the camera scenenode
	 * @param pos position to move to
 	*/
	void ExtendedCamera::setPosition(Vector3 pos)
	{
		mCameraNode->setPosition(pos);
	}
	
	/**
	 * Sets the target of the camera to follow
	 * @param dist distance between camera and target
	 * @pre the camera is not in manual mode
 	*/
	void ExtendedCamera::setDistanceToTarget(Vector3 dist)
	{
		mTargetDistance = dist;
		if(!mManualControl && mTargetNode && mFollowNode) {
			// sets position relative to parent (mTargetNode)
			mFollowNode->setPosition(dist);
			mCameraNode->setPosition(mFollowNode->getWorldPosition());
		}
	}

	/**
	 * Sets the scenenode target of the camera to follow
	 * @param target target to follow
	 * @post manual control has been disabled
 	*/
	void ExtendedCamera::setTarget(SceneNode * target)
	{
		mTargetNode = target;
		mFollowNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(mTargetNode->getName() + "_Follow_Node", Vector3(0, 0, 0));
		
		//mCameraNode->setPosition(mTargetNode->getWorldPosition());
		mCameraNode->setPosition(mTargetNode->getWorldPosition() + mTargetDistance);
		
		mTargetUp = mTargetNode->getOrientation() * Vector3::UNIT_Y;
		// Align the UP-vector of the camera scenenode to the target scenenode
		Vector3 localY = mCameraNode->getOrientation() * Vector3::UNIT_Y;
		Quaternion quat = localY.getRotationTo(mTargetUp);
		mCameraNode->rotate(quat, Node::TS_PARENT);

		this->lookAt(mTargetNode->getPosition());
		mManualControl = false;
		this->update(0.0);
	}


	/**
 	 * @return current camera target scenenode
 	*/
	const SceneNode * ExtendedCamera::getTarget()
	{
		return mTargetNode;
	}

	/**
 	 * Enables manual control of the camera, or reenables regular camera
 	 * @param man true if manual camera control is to be enabled, false if it is to be disabled
 	 * @pre if the manual control is disabled, the camera must be properly initialized before it is enabled (mCameraNode must be set)
 	*/
	void ExtendedCamera::enableManualControl(bool man)
	{
		if(man)
		{
			mManualControl = true;	
			mCameraNode->setAutoTracking(false);
		}
		else
		{
			mManualControl = false;
			mCameraNode->setAutoTracking(true, mTargetNode, Vector3::NEGATIVE_UNIT_Z, Vector3(0, 0, 0));
			mCameraNode->setPosition(mTargetNode->getWorldPosition());
			this->lookAt(mTargetNode->getPosition());
		}
	}
	
	/**
 	 * @return true if the camera is being manually controlled (does not follow a target)
 	*/
	bool ExtendedCamera::isManuallyControlled()
	{
		return mManualControl;
	}
	
	/**
	 * Controls the yaw of the camera scenenode
 	 * @return true if the operation is possible in current control mode
	 * @pre the camera is being manually controlled
 	*/
	bool ExtendedCamera::manualYaw(Radian rad)
	{
		if(!mManualControl)
			return false;
		else
		{
			mCameraNode->yaw(rad);
			return true;
		}
	}
	
	/**
	 * Controls the pitch of the camera scenenode
 	 * @return true if the operation is possible in current control mode
	 * @pre the camera is being manually controlled
 	*/
	bool ExtendedCamera::manualPitch(Radian rad)
	{
		if(!mManualControl)
			return false;
		else
		{
			mCameraNode->pitch(rad);
			return true;
		}
	}

	/**
	 * Controls the roll of the camera scenenode
 	 * @return true if the operation is possible in current control mode
	 * @pre the camera is being manually controlled
 	*/
	bool ExtendedCamera::manualRoll(Radian rad)
	{
		if(!mManualControl)
			return false;
		else
		{
			mCameraNode->roll(rad);
			return true;
		}
	}
	
	/**
	 * Enables/disables movement of the camera scenenode in manual mode
	 * @param dir direction in which to move
	 * @param move whether or not to move in the specified direction
 	 * @return true if the operation is possible in current control mode
	 * @pre the camera is being manually controlled
	 * @see MoveDirection
 	*/
	bool ExtendedCamera::move(ml278::MoveDirection dir, bool move)
	{
		if(!mManualControl)
			return false;
		else
		{
			mMovementDir[dir] = move;
			return true;
		}
	}

	/**
	 * Updates the position of the camera in manual of follow mode
	 * @param elapsedTime time elapsed since last call
 	*/
	void ExtendedCamera::update(Real elapsedTime)
	{
		// Follow mode
		if(!mManualControl)
		{
			// Target scenenode UP-vector
			mTargetUp = mTargetNode->getOrientation() * Vector3::UNIT_Y;

			// Set the position of the updatenode to the new cameraposition
			// Must set the new position manually, to prevent jumping (which occur when using child-node position)
			mFollowNode->setPosition(mTargetNode->getWorldPosition());
			mFollowNode->setOrientation(mTargetNode->getOrientation());
			mFollowNode->translate(mTargetDistance, Node::TS_LOCAL);
			
			// Move the cameranode to the updatenode
			mCameraNode->setPosition(mFollowNode->getWorldPosition());
					
			// Align the UP-vector of the camera scenenode to the target scenenode
			Vector3 localY = mCameraNode->getOrientation() * Vector3::UNIT_Y;
			Quaternion quat = localY.getRotationTo(mTargetUp);
			mCameraNode->rotate(quat, Node::TS_PARENT);

			// Point the camera at the target
			mCameraNode->lookAt(mTargetNode->getWorldPosition(), Ogre::Node::TS_WORLD, Vector3::NEGATIVE_UNIT_Z);	
			
			// Reset the earlier transformation to zero
			mFollowNode->translate(-mTargetDistance, Node::TS_LOCAL);
		}
		// Manual mode
		else
		{
			bool moving = false;
			Vector3 movementVector = Vector3(0.0,0.0,0.0);
			if(mMovementDir[FORWARD])
			{
				movementVector.z -= mMovementStep*elapsedTime;
				moving = true;
			}
			if(mMovementDir[BACKWARD])
			{
				movementVector.z += mMovementStep*elapsedTime;
				moving = true;
			}
			if(mMovementDir[STRAFE_LEFT])
			{
				movementVector.x -= mMovementStep * elapsedTime;
				moving = true;
			}
			if(mMovementDir[STRAFE_RIGHT])
			{
				movementVector.x += mMovementStep * elapsedTime;
				moving = true;
			}
			// Prevent setting a new position if it is unchanged
			if(moving)
				mCameraNode->setPosition((mCameraNode->getOrientation() * movementVector) + mCameraNode->getPosition());
		}
	}
}
